/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.builder.method;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jianggujin.dbfly.util.JAssert;
import com.jianggujin.dbfly.util.JStringUtils;

/**
 * 属性条件结构树
 * 
 * @author jianggujin
 *
 */
public class JPartTree implements Iterable<JPartTree.JOrPart> {

    private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\P{InBASIC_LATIN}))";
    private static final String QUERY_PATTERN = "select|find|read|get|query";
    private static final String DELETE_PATTERN = "delete|remove";
    private static final Pattern PREFIX_TEMPLATE = Pattern.compile( //
            "^(" + QUERY_PATTERN + "|" + DELETE_PATTERN + ")((\\p{Lu}.*?))??By");

    private final JSubject subject;

    private final JPredicate predicate;

    public JPartTree(String source) {

        JAssert.notNull(source, "Source must not be null");

        Matcher matcher = PREFIX_TEMPLATE.matcher(source);

        if (!matcher.find()) {
            this.subject = new JSubject(null);
            this.predicate = new JPredicate(source);
        } else {
            this.subject = new JSubject(matcher.group(0));
            this.predicate = new JPredicate(source.substring(matcher.group().length()));
        }
    }

    public Iterator<JOrPart> iterator() {
        return predicate.iterator();
    }

    public List<JOrPart> getOrPart() {
        return predicate.nodes;
    }

    public JSort getSort() {
        return predicate.getOrderBySource().toSort();
    }

    public boolean isDistinct() {
        return subject.isDistinct();
    }

    public boolean isValid() {
        return subject.isValid();
    }

    public boolean isSelect() {
        return subject.isSelect();
    }

    public boolean isDelete() {
        return subject.isDelete();
    }

    public boolean isExclude() {
        return subject.isExclude();
    }

    public List<String> getProperties() {
        return subject.getProperties();
    }

    public boolean hasPredicate() {
        return predicate.iterator().hasNext();
    }

    @Override
    public String toString() {
        return String.format("%s %s", JStringUtils.collectionToDelimitedString(predicate.nodes, " or "),
                predicate.getOrderBySource().toString()).trim();
    }

    private static String[] split(String text, String keyword) {
        Pattern pattern = Pattern.compile(String.format(KEYWORD_TEMPLATE, keyword));
        return pattern.split(text);
    }

    public static class JOrPart implements Iterable<JPart> {

        private final List<JPart> children;

        JOrPart(String source) {
            this.children = new ArrayList<JPart>();
            for (String part : split(source, "And")) {
                if (JStringUtils.hasText(part)) {
                    this.children.add(new JPart(part));
                }
            }
        }

        public Iterator<JPart> iterator() {
            return children.iterator();
        }

        @Override
        public String toString() {
            return JStringUtils.collectionToDelimitedString(children, " and ");
        }
    }

    private static class JSubject {

        private static final String DISTINCT = "Distinct";
        private static final String EXCLUDE = "Exclude";
        private static final Pattern DISTINCT_TEMPLATE = Pattern.compile( //
                "^(" + QUERY_PATTERN + ")" + DISTINCT);
        private static final Pattern QUERY_TEMPLATE = Pattern.compile("^(" + QUERY_PATTERN + ")");
        private static final Pattern DELETE_TEMPLATE = Pattern.compile("^(" + DELETE_PATTERN + ")");
        private static final Pattern COLUMN_BY_TEMPLATE = Pattern.compile( //
                "^(" + QUERY_PATTERN + ")(" + DISTINCT + ")?((\\p{Lu}.*?))??By");

        private final boolean distinct;
        private final boolean select;
        private final boolean delete;
        private final boolean exclude;
        private final List<String> properties;

        public JSubject(String subject) {
            if (JStringUtils.hasText(subject)) {
                this.select = QUERY_TEMPLATE.matcher(subject).find();
                this.delete = DELETE_TEMPLATE.matcher(subject).find();
                this.distinct = DISTINCT_TEMPLATE.matcher(subject).find();
                Matcher matcher = null;
                if (!this.delete && (matcher = COLUMN_BY_TEMPLATE.matcher(subject)).find()) {
                    String properties = matcher.group(3);
                    this.exclude = properties != null && properties.startsWith(EXCLUDE);
                    if (this.exclude) {
                        properties = properties.substring(EXCLUDE.length());
                    }
                    if (properties != null) {
                        this.properties = new ArrayList<String>();
                        for (String part : split(properties, "And")) {
                            if (JStringUtils.hasText(part)) {
                                this.properties.add(JStringUtils.firstCharToLowerCase(part));
                            }
                        }
                    } else {
                        this.properties = Collections.emptyList();
                    }
                } else {
                    this.exclude = false;
                    this.properties = Collections.emptyList();
                }
            } else {
                this.select = false;
                this.distinct = false;
                this.delete = false;
                this.exclude = false;
                this.properties = Collections.emptyList();
            }
        }

        public boolean isValid() {
            return select || delete;
        }

        public boolean isSelect() {
            return select;
        }

        public boolean isDistinct() {
            return distinct;
        }

        public boolean isDelete() {
            return delete;
        }

        public boolean isExclude() {
            return exclude;
        }

        public List<String> getProperties() {
            return properties;
        }

    }

    private static class JPredicate implements Iterable<JOrPart> {

        private static final String ORDER_BY = "OrderBy";

        private final List<JOrPart> nodes;
        private final JOrderBySource orderBySource;

        public JPredicate(String predicate) {

            String[] parts = split(predicate, ORDER_BY);

            if (parts.length > 2) {
                throw new IllegalArgumentException("OrderBy must not be used more than once in a method name!");
            }

            this.nodes = new ArrayList<JPartTree.JOrPart>();
            for (String part : split(parts[0], "Or")) {
                if (JStringUtils.hasText(part)) {
                    this.nodes.add(new JOrPart(part));
                }
            }

            this.orderBySource = parts.length == 2 ? new JOrderBySource(parts[1]) : JOrderBySource.EMPTY;
        }

        public JOrderBySource getOrderBySource() {
            return orderBySource;
        }

        @Override
        public Iterator<JOrPart> iterator() {
            return nodes.iterator();
        }
    }
}
